import * as COS from 'cos-nodejs-sdk-v5';
import { extname } from 'path';
import { nanoid } from 'nanoid';
import { config } from './generate.secretkey';
/**
 * Nano ID 5 仅适用于 ESM 项目、测试或 Node.js 脚本。 对于 CommonJS，您需要 Nano ID 3.x（我们仍然支持它）：
npm install nanoid@3
 */
declare interface Credentials {
    TmpSecretId: string,
    TmpSecretKey: string,
    SecurityToken: string,
    StartTime: number,
    ExpiredTime: number,
}
export class CosStorage {
    private static cos: COS;
    private static SecretId: string; // 加密id
    private static SecretKey: string; // 密钥
    private static Bucket: string = config.bucket; // 储存桶名称
    private static Region: string = config.region; // 地区
    private static agreement: string = 'http'; // http协议默认http
    private static pathDir: string = '/images/'; // 上传到那个文件夹下 默认images
    private static onProgress: any;
    private static mInstance: CosStorage = null;
    private static credentials: any = {}; // 临时密钥对象
    private static proxy = new Proxy(CosStorage.credentials, {
        set: (target, prop, val): boolean => { // 劫持传参改变创建不同的cos实列
            const keyNum = Object.keys(val).length;
            const params: any = {
                SecretId: CosStorage.SecretId,
                SecretKey: CosStorage.SecretKey,
            } 
            if(val && keyNum > 0) { // 有传临时密钥
                params.getAuthorization = (ops, callback) => callback(CosStorage.credentials)
            }
            CosStorage.cos = new COS(params)
            return true;
        }
    })
    constructor() {}
    // 注册cos实列
    static register(options = {}) {
        const { 
            SecretId = this.SecretId, 
            SecretKey = this.SecretKey, 
            onCredentials = null, // 自定义临时密钥回调函数
            Bucket = this.Bucket, 
            Region = this.Region, 
            pathDir = this.pathDir, 
            agreement = this.agreement,
            onProgress = null
        } = (options as any);
        const keyNum = Object.keys(CosStorage.credentials).length;
        if(SecretId === undefined && keyNum !== 0) {
            throw new Error('请配置密钥或临时密钥数据')
        }
        CosStorage.SecretId = SecretId;
        CosStorage.SecretKey = SecretKey;
        // 去过传入了临时密钥对象则使用临时密钥对象
        if(onCredentials && typeof onCredentials === 'function') {
            onCredentials((err, res: Credentials) => {
                CosStorage.proxy.credentials = res
                CosStorage.credentials = res
                CosStorage.SecretId = res.TmpSecretId;
                CosStorage.SecretKey = res.TmpSecretKey;
            })
        } else {
            CosStorage.proxy.credentials = {}
        }
        this.onProgress = onProgress || function () {};
        this.Bucket = Bucket;
        this.Region = Region;
        this.pathDir = pathDir;
        this.agreement = agreement;
        const params: any = {
            SecretId: SecretId,
            SecretKey: SecretKey,
        }
        CosStorage.cos = new COS(params)
        if (CosStorage.mInstance == null) {
          return new CosStorage()
        } else {
          return CosStorage.mInstance;
        }
    }
    // 文件名称加密
    private getFileName(file) {
        const suffix = extname(file.originalname);
        return nanoid(16) + suffix
    }
    // 实现 MulterOptions.storage 原型链方法
    _handleFile (req, file, callback) {
      const chunks  = [];
      const stream = file.stream;
      // 当有数据可从流中读取时，就会触发 'readable' 事件。
      // 当到达流数据的尽头时， 'readable' 事件也会触发，但是在 'end' 事件之前触发。
      // 'readable' 事件表明流有新的动态：要么有新的数据，要么到达流的尽头。
      // 对于前者，stream.read() 会返回可用的数据。 对于后者，stream.read() 会返回 null
      stream.on("readable", () => {
        let chunk;
        // stream.setEncoding('utf8')
        while (null !== (chunk = stream.read())) {
            chunks.push(chunk);
        }
      });
      // 监听stram流读取完成
      stream.on("end", () => {
        const buffer = Buffer.concat(chunks)
        const params = {
          Bucket: CosStorage.Bucket,
          Region: CosStorage.Region,
          Key: CosStorage.pathDir+this.getFileName(file),
          Body: buffer,
          onProgress: (progressData) => {
            CosStorage.onProgress(progressData);
          },
        }
        file.filename = params.Key;
        const putCallback = (err, data) => {
            if(err) return callback(err)
            file.url = `${CosStorage.agreement}://${data.Location}`
            callback(null, data)
        }
        CosStorage.cos.putObject(params, putCallback)
      });
    }
    // 实现 MulterOptions.storage 原型链方法
    _removeFile(req, file, callback) {
        const params = {
            Bucket: CosStorage.Bucket,
            Region: CosStorage.Region,
            Key: file.filename,
        }
        const delCallback = (err, data) => {
            if (err) {
                return callback(err.error);
            } else {
                return callback(null, data);
            }
        }
        CosStorage.cos.deleteObject(params, delCallback);
    }
  }